package com.atguigu.spzx.common.aspect;

import com.alibaba.fastjson2.JSON;
import com.atguigu.spzx.common.annotation.GuiguCache;
import com.atguigu.spzx.common.constant.RedisConst;
import com.atguigu.spzx.model.vo.h5.ProductItemVo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

/**
 * 当符合切入点表达式的方法被调用，那么，切面通知方法会进行功能扩展。
 * 给目标方法增加查询缓存，并利用分布式锁解决缓存击穿问题。
 */
@Aspect //声明这是一个切面类
@Component //将切面类对象纳入 IOC 容器中
@Slf4j  //打印日志用的
public class GuiguCacheAspect {

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    //声明通知方法，采用环绕通知进行功能扩展。


    @Around("@annotation(guiguCache)")
    public Object cacheAroundAspect(ProceedingJoinPoint proceedingJoinPoint, GuiguCache guiguCache) {
        ProductItemVo productItemVo = null;
        //***********************************************************************************
        // TODO  一会搬家
//        Long skuId = Long.parseLong(proceedingJoinPoint.getArgs()[0].toString());
//
//        //校验
//        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConst.PRODUCT_BLOOM_FILTER);
//        if(!bloomFilter.contains(skuId)){ //判断商品在布隆过滤器中是否存在？
//            productItemVo = new ProductItemVo();
//            log.info("from RBloomFilter data not exists 返回空对象= "+ productItemVo);
//            return productItemVo;
//        }

        //***********************************************************************************


        //1.先走缓存，缓存有直接返回。效率大大提高
        String cacheKeyExpression = guiguCache.cacheKey(); //   例如： RedisConst.SKUKEY_PREFIX + "#{#params[0]}" + RedisConst.SKUKEY_SUFFIX
        String cacheKey = paraseExpression(proceedingJoinPoint, cacheKeyExpression, String.class);
        String cacheJson = redisTemplate.opsForValue().get(cacheKey);

        if (StringUtils.hasText(cacheJson)) {
            Type methodReturnType = getMethodType(proceedingJoinPoint);
            Object result = JSON.parseObject(cacheJson, methodReturnType);
            log.info("from redis data result=" + result);
            return result;
        }

        //解决缓存击穿问题，采用Redisson提供分布式锁机制来解决。
        boolean enableLock = guiguCache.enableLock();  //注解：enableLock取值true表示开启分布式锁，否则，不用开启分布式锁。
        if (enableLock) {

            String lockKeyExpression = guiguCache.lockName();  // 例如：RedisConst.PRODUCT_LOCK_SUFFIX+ "#{#params[0]}")
            String lockKey = paraseExpression(proceedingJoinPoint, lockKeyExpression, String.class);
            RLock lock = redissonClient.getLock(lockKey);
            boolean tryLock = lock.tryLock(); //  30秒自动释放锁   看门狗    过期时间/3       默认过期时间也可以修改。需要重置Config配置  org.redisson.config.Config.lockWatchdogTimeout
            if (tryLock) {
                try {
                    //2.缓存没有走数据库，将数据存放缓存一份。给下次请求利用缓存，提高效率
                    Object result = proceedingJoinPoint.proceed(); //执行目标方法，从数据库获取数据。
                    if (result == null) {
                        result = new Object();
                        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                                RedisConst.SKUKEY_EMPTY_TIMEOUT, TimeUnit.SECONDS);
                        log.info("lock from mysql data null obj=" + result);
                    } else {
                        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                                RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        log.info("lock from mysql data obj=" + result);
                    }
                    return result;
                } catch (Throwable e) {
                    throw new RuntimeException("GuiguCacheAspect异常");
                } finally {
                    //释放锁
                    lock.unlock();
                }
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return cacheAroundAspect(proceedingJoinPoint, guiguCache);
            }

        } else {


            try {
                //2.缓存没有走数据库，将数据存放缓存一份。给下次请求利用缓存，提高效率
                Object result = proceedingJoinPoint.proceed(); //执行目标方法，从数据库获取数据。
                if (result == null) {
                    result = new Object();
                    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                            RedisConst.SKUKEY_EMPTY_TIMEOUT, TimeUnit.SECONDS);
                    log.info("not lock from mysql data null obj=" + result);
                } else {
                    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                            RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    log.info("not lock from mysql data obj=" + result);
                }
                return result;
            } catch (Throwable e) {
                e.printStackTrace();
                return new Object();
            }

        }


    }



    /*@Around("@annotation(guiguCache)")
    public Object cacheAroundAspect(ProceedingJoinPoint proceedingJoinPoint, GuiguCache guiguCache){
        ProductItemVo productItemVo = null;
        //***********************************************************************************

        Long skuId = Long.parseLong(proceedingJoinPoint.getArgs()[0].toString());

        //校验
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConst.PRODUCT_BLOOM_FILTER);
        if(!bloomFilter.contains(skuId)){ //判断商品在布隆过滤器中是否存在？
            productItemVo = new ProductItemVo();
            log.info("from RBloomFilter data not exists 返回空对象= "+ productItemVo);
            return productItemVo;
        }

        //***********************************************************************************
        //1.先走缓存，缓存有直接返回。效率大大提高
        String productItemVoJson = redisTemplate.opsForValue().get(RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX);
        if(StringUtils.hasText(productItemVoJson)){
            productItemVo = JSON.parseObject(productItemVoJson, ProductItemVo.class);
            log.info("from redis data ProductItemVo="+productItemVo);
            return productItemVo;
        }

        //解决缓存击穿问题，采用Redisson提供分布式锁机制来解决。
        String key = RedisConst.PRODUCT_LOCK_SUFFIX+skuId;
        RLock lock = redissonClient.getLock(key);
        boolean tryLock = lock.tryLock(); //  30秒自动释放锁   看门狗    过期时间/3       默认过期时间也可以修改。需要重置Config配置  org.redisson.config.Config.lockWatchdogTimeout
        if(tryLock){
            try {
                //2.缓存没有走数据库，将数据存放缓存一份。给下次请求利用缓存，提高效率
                productItemVo = (ProductItemVo) proceedingJoinPoint.proceed(); //执行目标方法，从数据库获取数据。
                if(productItemVo==null){
                    productItemVo = new ProductItemVo();
                    redisTemplate.opsForValue().set(RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_EMPTY_TIMEOUT,JSON.toJSONString(productItemVo),
                            RedisConst.SKUKEY_EMPTY_TIMEOUT, TimeUnit.SECONDS);
                    log.info("from mysql data ProductItemVo="+productItemVo);
                }else{
                    redisTemplate.opsForValue().set(RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX,JSON.toJSONString(productItemVo),
                            RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    log.info("from mysql data ProductItemVo="+productItemVo);
                }
                return productItemVo;
            } catch (Throwable e) {
                throw new RuntimeException("GuiguCacheAspect异常");
            } finally {
                //释放锁
                lock.unlock();
            }
        }else {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return cacheAroundAspect(proceedingJoinPoint,guiguCache);
        }

    }*/


    // SpelExpressionParser是线程安全的
    private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    // 获取缓存key
    public <T> T paraseExpression(ProceedingJoinPoint proceedingJoinPoint, String cacheKey, Class<T> clazz) {
        Expression expression = spelExpressionParser.parseExpression(cacheKey, ParserContext.TEMPLATE_EXPRESSION);

        EvaluationContext evaluationContext = new StandardEvaluationContext();
        evaluationContext.setVariable("params", proceedingJoinPoint.getArgs());

        T cacheKeyData = expression.getValue(evaluationContext, clazz);
        return cacheKeyData;
    }

    // 获取目标方法的返回值类型
    public Type getMethodType(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Type genericReturnType = method.getGenericReturnType(); //获取目标方法的返回结果带泛型的类型
        return genericReturnType;
    }
}
